{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e2bb476",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras.layers import Dropout, Dense, SimpleRNN\n",
    "import matplotlib.pyplot as plt\n",
    "import os\n",
    "import pandas as pd\n",
    "from sklearn.preprocessing import MinMaxScaler\n",
    "from sklearn.metrics import mean_squared_error, mean_absolute_error\n",
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8957e76",
   "metadata": {},
   "outputs": [],
   "source": [
    "for module in np, pd, tf:\n",
    "     print (module.__name__, module.__version__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "247a90dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这两行代码解决 plt 中文显示的问题\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']\n",
    "plt.rcParams['axes.unicode_minus'] = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f298f448",
   "metadata": {},
   "outputs": [],
   "source": [
    "stock = pd.read_csv('../dataset/600031.csv')  # 读取股票文件\n",
    "# stock = stock.drop(['成交额'],axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15bdcbdf",
   "metadata": {},
   "source": [
    "### 用多特征预测股价\n",
    "### 将数据集分成训练集：测试集 == 4:1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c46c880d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 取除日期外全部特征\n",
    "training_set = stock.iloc[0:int(len(stock) / 5 * 4), 1:]\n",
    "test_set = stock.iloc[int(len(stock) / 5 * 4):, 1:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fcbfd899",
   "metadata": {},
   "outputs": [],
   "source": [
    "training_set.shape , test_set.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47a05095",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "_training_set = training_set\n",
    "_test_set = test_set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10b83df9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "_training_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c711cd70",
   "metadata": {},
   "outputs": [],
   "source": [
    "_test_set.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd18ed99",
   "metadata": {},
   "source": [
    "# 移动平均处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2f358de8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def mean_handle(total_list, cycle):\n",
    "    def handle(_list):\n",
    "        datalist = list()\n",
    "        for i in range(len(_list)):\n",
    "            # 最新数据在列表的后面\n",
    "            # 1. 收盘价/均值\n",
    "            datalist.append(_list[i] / np.mean(_list))\n",
    "        return datalist\n",
    "\n",
    "    # 根据周期分组\n",
    "    new_list = list()\n",
    "    for _ in range(0, int(len(total_list) / cycle)):\n",
    "        new_list.append(total_list[_ * cycle:(_ + 1) * cycle])\n",
    "    if len(new_list) * cycle != len(total_list):\n",
    "        new_list.append(total_list[len(new_list) * cycle - len(total_list):])\n",
    "\n",
    "    data = list()\n",
    "    for value in [handle(list(map(float, data_list))) for data_list in new_list]:\n",
    "        data.extend(value)\n",
    "\n",
    "    return data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "beccfa03",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for column in training_set.columns:\n",
    "    training_set[column] = mean_handle(training_set[column].tolist(),30)\n",
    "for column in test_set.columns:\n",
    "    test_set[column] = mean_handle(test_set[column].tolist(),30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8180442c",
   "metadata": {},
   "outputs": [],
   "source": [
    "training_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae16b4c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33122ab7",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "training_set = training_set.values\n",
    "test_set = test_set.values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3c6b862",
   "metadata": {},
   "source": [
    "# 归一化处理（进行缩放）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2ee46e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 归一化\n",
    "sc = MinMaxScaler(feature_range=(0, 1))  # 定义归一化：归一化到(0，1)之间\n",
    "# 缩放\n",
    "training_set_scaled = sc.fit_transform(training_set)  # 求得训练集的最大值，最小值这些训练集固有的属性，并在训练集上进行归一化\n",
    "test_set = sc.transform(test_set)  # 利用训练集的属性对测试集进行归一化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9310a93f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 参数设置\n",
    "# 步进值\n",
    "seq_len = 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6b53e2d",
   "metadata": {},
   "source": [
    "# 训练集处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4720fa9",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_train = []\n",
    "y_train = []\n",
    "\n",
    "# 数据进行分组\n",
    "for i in range(seq_len, len(training_set_scaled)):\n",
    "    # 全部特征\n",
    "    x_train.append(training_set_scaled[i - seq_len:i, 0:])\n",
    "    # 收盘价\n",
    "    y_train.append(training_set_scaled[i, list(stock.iloc[0:int(len(stock) / 5 * 4), 1:].columns).index('收盘')])\n",
    "\n",
    "# 对训练集进行打乱\n",
    "np.random.seed(7)\n",
    "np.random.shuffle(x_train)\n",
    "np.random.seed(7)\n",
    "np.random.shuffle(y_train)\n",
    "tf.random.set_seed(7)\n",
    "\n",
    "# 将训练集由list格式变为array格式\n",
    "x_train, y_train = np.array(x_train), np.array(y_train)\n",
    "x_train = np.reshape(x_train, (x_train.shape[0], seq_len, training_set.shape[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f4d9e55",
   "metadata": {},
   "source": [
    "# 测试集处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "807b27a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_test = []\n",
    "y_test = []\n",
    "\n",
    "# 利用for循环，遍历整个测试集，提取测试集中连续5天的开盘价作为输入特征x_train，第6天的数据作为标签\n",
    "for i in range(seq_len, len(test_set)):\n",
    "    # 全部特征\n",
    "    x_test.append(test_set[i - seq_len:i, 0:])\n",
    "    # 收盘价\n",
    "    y_test.append(test_set[i, list(stock.iloc[0:int(len(stock) / 5 * 4), 1:].columns).index('收盘')])\n",
    "\n",
    "    # 测试集变array并reshape为符合RNN输入要求：[送入样本数， 循环核时间展开步数， 每个时间步输入特征个数]\n",
    "x_test, y_test = np.array(x_test), np.array(y_test)\n",
    "x_test = np.reshape(x_test, (x_test.shape[0], seq_len, test_set.shape[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e45bc36",
   "metadata": {},
   "source": [
    "# 构建模型\n",
    "\n",
    "### 序列化建模，一般步骤为：\n",
    "\n",
    "　　* 1、实例化一个Sequential类，该类是继承于Model类；\n",
    "\n",
    "　　* 2、添加所需要的神经网络层；\n",
    "\n",
    "　　* 3、用compile进行编译模型；\n",
    "\n",
    "　　* 4、用fit训练模型；\n",
    "\n",
    "　　* 5、用predict预测。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f2d3b77",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = tf.keras.Sequential([\n",
    "    SimpleRNN(80, return_sequences=True),\n",
    "    Dropout(0.2),\n",
    "    SimpleRNN(100),\n",
    "    Dropout(0.2),\n",
    "    Dense(1)\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6fb9d91",
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(optimizer=tf.keras.optimizers.Adam(0.001),loss='mean_squared_error')  \n",
    "# 损失函数用均方误差\n",
    "# 该应用只观测loss数值，不观测准确率，所以删去metrics选项，一会在每个epoch迭代显示时只显示loss值"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4c9c0db",
   "metadata": {},
   "source": [
    "# 预测一次，并保存预测结构，用于之后的验证"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1f8a0afa",
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoint_save_path = \"./checkpoint/rnn_stock.ckpt\"\n",
    "if os.path.exists(checkpoint_save_path + '.index'):\n",
    "    print('-------------load the model-----------------')\n",
    "    model.load_weights(checkpoint_save_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e071782a",
   "metadata": {},
   "source": [
    "# 参数详解：\n",
    "* x=None, #输入的x值\n",
    "* y=None, #输入的y标签值\n",
    "* batch_size=None, #整数 ，每次梯度更新的样本数即批量大小。未指定，默认为32。\n",
    "* epochs=1, #迭代次数\n",
    "* verbose=1, #整数，代表以什么形式来展示日志状态，\n",
    "* verbose = 0 为不在标准输出流输出日志信息，verbose = 1 为输出进度条记录，verbose = 2 为每个epoch输出一行记录\n",
    "* callbacks=None, #回调函数，这个list中的回调函数将会在训练过程中的适当时机被调用，参考回调函数\n",
    "* validation_split=0.0, #浮点数0-1之间，用作验证集的训练数据的比例。模型将分出一部分不会被训练的验证数据，并将在每一轮结束时评估这些验证数据的误差和任何其他模型指标。\n",
    "* validation_data=None, #这个参数会覆盖 validation_split\n",
    "   即两个函数只能存在一个，它的输入为元组 (x_val，y_val)，这作为验证数据。\n",
    "* shuffle=True, #布尔值。是否在每轮迭代之前混洗数据\n",
    "* class_weight=None,\n",
    "* sample_weight=None, \n",
    "* initial_epoch=0, \n",
    "* steps_per_epoch=None, #一个epoch包含的步数（每一步是一个batch的数据送入）\n",
    "   当使用如TensorFlow数据Tensor之类的输入张量进行训练时，默认的None代表自动分割，即数据集样本数/batch样本数。\n",
    "* validation_steps=None, #在验证集上的step总数，仅当steps_per_epoch被指定时有用。\n",
    "* validation_freq=1, #指使用验证集实施验证的频率。当等于1时代表每个epoch结束都验证一次\n",
    "* max_queue_size=10,\n",
    "* workers=1,\n",
    "* use_multiprocessing=False   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9cc9532d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 训练参数\n",
    "batch_size = 64\n",
    "epochs = 100\n",
    "validation_freq=1\n",
    "# 等等"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5da0f3b6",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,\n",
    "                                                 save_weights_only=True,\n",
    "                                                 save_best_only=True,\n",
    "                                                 monitor='val_loss')\n",
    "\n",
    "_model = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test), validation_freq=validation_freq,\n",
    "                   callbacks=[cp_callback])\n",
    "\n",
    "model.summary()\n",
    "\n",
    "file = open('./weights.txt', 'w')  # 参数提取\n",
    "for v in model.trainable_variables:\n",
    "    file.write(str(v.name) + '\\n')\n",
    "    file.write(str(v.shape) + '\\n')\n",
    "    file.write(str(v.numpy()) + '\\n')\n",
    "file.close()\n",
    "\n",
    "loss = _model.history['loss']\n",
    "val_loss = _model.history['val_loss']\n",
    "\n",
    "plt.plot(loss, label='训练损失')\n",
    "plt.plot(val_loss, label='测试损失')\n",
    "plt.title('训练和测试损失')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf550192",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 预测\n",
    "# 测试集输入模型进行预测\n",
    "predicted_stock_price = model.predict(x_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "368f9504",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 归一时是对所有数据列进行的归一\n",
    "# 补充数据列之后才能进行反归一\n",
    "predicted_df = pd.DataFrame(test_set[seq_len:])\n",
    "predicted_df.iloc[:, list(stock.iloc[0:int(len(stock) / 5 * 4), 1:].columns).index('收盘')] = predicted_stock_price\n",
    "predicted_df.head()\n",
    "predicted_set = predicted_df.values\n",
    "# 对预测数据还原---从（0，1）反归一化到原始范围\n",
    "predicted_stock_price = sc.inverse_transform(predicted_set)\n",
    "# 对真实数据还原---从（0，1）反归一化到原始范围\n",
    "real_stock_price = sc.inverse_transform(test_set[seq_len:])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c51d3e9",
   "metadata": {},
   "source": [
    "# 反推股价"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab69c7b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def mean_inverse(total_list, mean_list, cycle):\n",
    "    def inverse(_list, _mean):\n",
    "        datalist = list()\n",
    "        for i in range(len(_list)):\n",
    "            # 最新数据在列表的后面\n",
    "            # 1. 收盘价/均值\n",
    "            datalist.append(_mean[i] * np.mean(_list))\n",
    "        return datalist\n",
    "\n",
    "    # 原来数据\n",
    "    # 根据周期分组\n",
    "    data_list = list()\n",
    "    for _ in range(0, int(len(total_list) / cycle)):\n",
    "        data_list.append(total_list[_ * cycle:(_ + 1) * cycle])\n",
    "    if len(data_list) * cycle != len(total_list):\n",
    "        data_list.append(total_list[len(data_list) * cycle - len(total_list):])\n",
    "\n",
    "    # 移动平均数据\n",
    "    ma_list = list()\n",
    "    for _ in range(0, int(len(mean_list) / cycle)):\n",
    "        ma_list.append(mean_list[_ * cycle:(_ + 1) * cycle])\n",
    "    if len(ma_list) * cycle != len(mean_list):\n",
    "        ma_list.append(mean_list[len(data_list) * cycle - len(mean_list):])\n",
    "\n",
    "    data = list()\n",
    "    for i in range(len(data_list)):\n",
    "        value = inverse(list(map(float, data_list[i])), ma_list[i])\n",
    "        data.extend(value)\n",
    "\n",
    "    return data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "611efc66",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "training_set = pd.DataFrame(training_set)\n",
    "training_set.columns = list(stock.columns)[1:]\n",
    "test_set = pd.DataFrame(test_set)\n",
    "test_set.columns = list(stock.columns)[1:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0eb4f2f5",
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "_training_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bca96a03",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "_test_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f6c2b98",
   "metadata": {},
   "outputs": [],
   "source": [
    "for column in training_set.columns:\n",
    "    training_set[column] = mean_inverse(_training_set[column].tolist(),training_set[column].tolist(),30)\n",
    "for column in test_set.columns:\n",
    "    test_set[column] = mean_inverse(_test_set[column].tolist(),test_set[column].tolist(),30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17e7b60d",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "training_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1eee6df5",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_set.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2dc1b548",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 只取收盘价\n",
    "real_stock_price = real_stock_price[:,list(stock.iloc[0:int(len(stock) / 5 * 4), 1:].columns).index('收盘')]\n",
    "predicted_stock_price = predicted_stock_price[:,list(stock.iloc[0:int(len(stock) / 5 * 4), 1:].columns).index('收盘')]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b968b0a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 画出真实数据和预测数据的对比曲线\n",
    "plt.plot(real_stock_price, color='red', label='股票价格')\n",
    "plt.plot(predicted_stock_price, color='blue', label='预测股票价格')\n",
    "plt.title('股票价格预测')\n",
    "plt.xlabel('时间')\n",
    "plt.ylabel('股票价格')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b2fbfd3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 评估\n",
    "# calculate MSE 均方误差 ---> E[(预测值-真实值)^2] (预测值减真实值求平方后求均值)\n",
    "mse = mean_squared_error(predicted_stock_price, real_stock_price)\n",
    "# calculate RMSE 均方根误差--->sqrt[MSE]    (对均方误差开方)\n",
    "rmse = math.sqrt(mean_squared_error(predicted_stock_price, real_stock_price))\n",
    "# calculate MAE 平均绝对误差----->E[|预测值-真实值|](预测值减真实值求绝对值后求均值）\n",
    "mae = mean_absolute_error(predicted_stock_price, real_stock_price)\n",
    "print('均方误差: %.6f' % mse)\n",
    "print('均方根误差: %.6f' % rmse)\n",
    "print('平均绝对误差: %.6f' % mae)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb5827cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "#ACC\n",
    "error = 0\n",
    "summery = 0\n",
    "for i in range(24):\n",
    "    error += abs(predicted_stock_price[i] - real_stock_price[i])\n",
    "    summery += real_stock_price[i]\n",
    "acc = 1 - error/summery\n",
    "print(\"准确率：{}\".format(acc))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "deep",
   "language": "python",
   "name": "deep"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
